﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Algobox.JuicyLib.Arithmetic.Synchronised
{
    public class DateTimeMovingAverage
    {
        /// <summary>
        /// Create a new moving average calculator.
        /// </summary>
        /// <param name="windowSize">The maximum number of values to be considered
        /// by this moving average calculation.</param>
        /// <exception cref="ArgumentOutOfRangeException">If windowSize less than one.</exception>
        public DateTimeMovingAverage(TimeSpan windowSize)
        {
            if (windowSize <= TimeSpan.Zero)
                throw new ArgumentOutOfRangeException("WindowSize", windowSize, "Window size must be greater than zero.");

            Data = new Dictionary<DateTime, double>();
            WindowSize = windowSize;
        }

        bool isMature = false;
        public bool IsMature { get { return isMature; } }

        DateTime? startTime = null;

        Dictionary<DateTime, double> Data;

        TimeSpan WindowSize;

        double CurrentValue;

        public double Average
        {
            get
            {
                LockCurrentValue.EnterReadLock();
                try
                {
                    return CurrentValue;
                }
                finally
                {
                    LockCurrentValue.ExitReadLock();
                }
            }
        }

        public override string ToString() { return Average.ToString(); }

        ReaderWriterLockSlim LockData = new ReaderWriterLockSlim();
        ReaderWriterLockSlim LockCurrentValue = new ReaderWriterLockSlim();


        /// <summary>
        /// Updates the moving average with its next value, and returns the updated average value.
        /// When IsMature is true and NextValue is called, a previous value will 'fall out' of the
        /// moving average.
        /// </summary>
        public double NextValue(DateTime timeStamp, double nextValue)
        {
            WaitCallback fire = (tester) =>
                {
                    // Update and clean average
                    LockData.EnterWriteLock();
                    try
                    {
                        Data[timeStamp] = nextValue;
                        Data = Data.Where(elements => elements.Key > timeStamp - WindowSize).ToDictionary(item => item.Key, item => item.Value);
                    }
                    finally
                    {
                        LockData.ExitWriteLock();
                    }

                    // Check maturity and set current average
                    LockCurrentValue.EnterWriteLock();
                    LockData.EnterReadLock();
                    try
                    {
                        if (!isMature)
                        {
                            if (!startTime.HasValue)
                                startTime = timeStamp;

                            if (timeStamp >= startTime.Value + WindowSize)
                                isMature = true;
                        }
                        CurrentValue = Data.Average(element => element.Value);
                    }
                    finally
                    {
                        LockCurrentValue.ExitWriteLock();
                        LockData.ExitReadLock();
                    }
                };

            ThreadPool.QueueUserWorkItem(fire);

            return Average;
        }


        /// <summary>
        /// Clears any accumulated state and resets the calculator to its initial configuration.
        /// Calling this method is the equivalent of creating a new instance.
        /// </summary>
        public void Reset()
        {
            LockData.EnterWriteLock();
            try
            {
                Data.Clear();
            }
            finally
            {
                LockData.ExitWriteLock();
            }
        }
    }





}
